package net.ashrentum.utils;

import java.util.Random;
import java.util.regex.Matcher;

public final class Interval<T extends java.lang.Number> implements UniformSampeable<T> {
	
	/* All interval data types are internally converted to a primitive double */
	
	/** Lower limit. */
	private double lower; 
	/** Upper limit. */
	private double upper;
	
	private IntervalType type;
	private IntervalDataType dataType;
	
	/* Constant values of an interval once defined */
	private String stringRepresentation;
	private double length;
	
		
	
	Interval(String interval) throws Exception {
		setType(interval);
	}
	
	void setType(String interval) throws Exception {
		Matcher m;		
		for (IntervalType t : IntervalType.values()) {
			m = t.pattern.matcher(interval);
			if (m.matches()) {
				type = t;
				lower = Double.parseDouble(m.group(IntervalType.LOWER_LIMIT));
				upper = Double.parseDouble(m.group(IntervalType.UPPER_LIMIT));
				dataType = 
					IntervalDataType.getDataType(m.group(IntervalType.LOWER_LIMIT), m.group(IntervalType.UPPER_LIMIT));
			}
				
		}
		if (type == null)
			throw new Exception("Interval Syntax not recognized: " + interval); //TODO create specific Exception
		
		setConstants();
	}
		
	Interval(int lower, int upper, IntervalType type) {
		this((double) lower, (double) upper, type);
		this.dataType = IntervalDataType.INTEGER;
	}
	
	Interval(long lower, long upper, IntervalType type) {
		this((double) lower, (double) upper, type);
		this.dataType = IntervalDataType.INTEGER;
	}
	
	Interval(double lower, double upper, IntervalType type) {
		this.lower = lower; 
		this.upper = upper;
		this.type  = type;
		this.dataType = IntervalDataType.FLOAT;
		setConstants();
	}
	
	private void setConstants() {
		setStringRepresentation();
		setLength();
	}
	
	private void setStringRepresentation() {
		if (dataType == IntervalDataType.FLOAT)
			stringRepresentation = type.leftToken +       lower + ", " +       upper + type.rightToken;
		else
			stringRepresentation = type.leftToken + (int) lower + ", " + (int) upper + type.rightToken;
	}
	
	@Override
	public String toString() {
		return stringRepresentation;
	}

    private void setLength() {
    	double toSubstract = 0;
    	if (type == IntervalType.INC_EXC || type == IntervalType.EXC_INC)
    		toSubstract = Double.MIN_VALUE;
    	else if (type == IntervalType.EXC_EXC)
    		toSubstract = 2 * Double.MIN_VALUE;
    	length = upper - lower - toSubstract; 
    }
    
    /** 
     * Returns the intervals' length. 
     * <p>
     * Note: If the interval is not strictly inclusive in both sides, the total length will have a substraction
     * for each non-inclusive side. This substraction is equal to the minimum value the JVM compiler supports, 
     * @see Double.MIN_VALUE
     * 
     * @return interval's length
     */
    public final double length() {
    	return length;
    }
    
    public final boolean contains(double x) {
    	if (type == IntervalType.EXC_EXC)
    		return lower < x && x < upper;
    	if (type == IntervalType.INC_EXC)
    		return lower <= x && x < upper;
    	if (type == IntervalType.EXC_INC)
    		return lower < x && x <= upper;
    	//if (type == IntervalType.EXC_INC)
    		return lower <= x && x <= upper;
    }
    
    /**
     * Returns union between the calling Interval Object and the given Interval Object.
     * 
     * @param b Interval to make the union with
     * @return new union Interval Object
     */
    public Interval<? extends java.lang.Number> union(Interval<? extends java.lang.Number> b) {
    	double lower;
    	double upper;
    	//lower limit flag. Different uses, see below
    	boolean l = true; 
    	//upper limit flag. Different uses, see below.
    	boolean u = true;
    	
    	/* Lower limit value */
    	if (b.lower < this.lower || (b.lower == this.lower && 
    			(b.type == IntervalType.INC_EXC || b.type == IntervalType.INC_INC))) {
    		lower = b.lower;
    		l = false; //lower limit determined by object b
    	}
    	else
    		lower = this.lower;
    	
    	/* Upper limit value */
    	if (b.upper > this.upper || (b.upper == this.upper && 
    			(b.type == IntervalType.EXC_INC || b.type == IntervalType.INC_INC))) {
    		upper = b.upper;
    		u = false;
    	}    		
    	else
    		upper = this.upper;
    	
    	/* Define Type */
    	IntervalType auxType;
    	auxType = (l) ? this.type : b.type;
    	l = (auxType == IntervalType.INC_EXC || auxType == IntervalType.INC_INC); //lower limit inclusive
    	auxType = (u) ? this.type : b.type;
    	u = (auxType == IntervalType.EXC_INC || auxType == IntervalType.INC_INC); //upper limit inclusive
    	
    	if (l && u) auxType = IntervalType.INC_INC;
    	else if (l) auxType = IntervalType.INC_EXC;
    	else if (u) auxType = IntervalType.EXC_INC;
    	else 		auxType = IntervalType.EXC_EXC;
    	
    	/* Define Data Type & create new object*/
    	IntervalDataType auxDataType;
		if (b.dataType.ordinal() < this.dataType.ordinal())
			auxDataType = b.dataType;
		else
			auxDataType = this.dataType;
		
		if (auxDataType == IntervalDataType.FLOAT)
			return new Interval<Double>(lower, upper, auxType);
		else //if (auxDataType == IntervalDataType.INTEGER)
			return new Interval<Integer>((int) lower, (int) upper, auxType);
    }


    //TODO create methods intersects & overlaps
	
	public static void main(String[] args) throws Exception {
		Interval a; 
		a = new Interval("  (  3  ,   5   ) ");
		System.out.println(a + " " + (double) a.length);
		a = new Interval("(3, 8]");
		System.out.println(a + " " + a.length);
		
		a = a.union(new Interval<Integer>("[3, 9.6]"));
		System.out.println(a + " " + a.length + " " + a.nextUniform());
	}

	@Override
	public T nextUniform() {
		return auxNextUniform(new Random());
	}

	@Override
	public T nextUniform(long seed) {
		return auxNextUniform(new Random(seed));
	}
	
	@SuppressWarnings("unchecked")
	private T auxNextUniform(Random r) {
//		if (dataType == IntervalDataType.FLOAT)
//			return (T) lower + r.nextDouble() * (upper - lower); //TODO substract if necessary
//		if (dataType == IntervalDataType.INTEGER)
//			return lower + r.nextInt((int) (upper - lower));
		
		T a = lower + upper;
		
		Double n = new Double(3);
		return (T) n;
		
		//return null;
	}
}
